﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Bettzueche.Dampfross
{
    /// <summary>
    /// Eine im Gesamten rechteckige Karte aus Hexagonen.
    /// </summary>
    /// <typeparam name="T">Typ eine Hexagon-Implementierung, mit Standardkonstruktor</typeparam>
    [Serializable]
    public class HexMapRectFlat<T> : HexMapRectFlat, IEnumerable<T>
        where T : Hexagon, new()
    {

        T[,] _map; // das Array, Zugriff nur über Indexer!


        #region Properties / Indexer

        /// <summary>
        /// Gets or sets the <c>Hexagon</c> of the specified row, col (Hex-Coord) in this Hex grid.
        /// </summary>
        /// <value>
        /// The <see cref="Hexagon{T}"/>. Falls Koordinate außerhalb liegt, wird der Set-Befehl ignoriert.
        /// </value>
        /// <param name="row">The row.</param>
        /// <param name="col">The col.</param>
        /// <returns>null, wenn Index außerhalb ist; das Hexagon sonst.</returns>
        /// <exception cref="System.IndexOutOfRangeException">wenn Koordinate nicht in der Karte liegt</exception>
        public T this[int row, int col] {
            get { return _map[row + (col / 2), col]; }
            set { _map[row + (col / 2), col] = value; }
        }

        /// <summary>
        /// Gets or sets the <see cref="Hexagon{T}"/> with the specified coord.
        /// </summary>
        /// <value>
        /// The <see cref="Hexagon{T}"/>.
        /// </value>
        /// <param name="coord">The coord.</param>
        /// <returns></returns>
        public T this[HexKoordinate coord] {
            get { return this[coord.Row, coord.Col]; }
            set { this[coord.Row, coord.Col] = value; }
        }

        /// <summary>
        /// Gibt die Anzahl der Kacheln dieser Karte
        /// </summary>
        /// <value>
        /// The count.
        /// </value>
        public int Count {
            get { return _map.Length; }
        }

        /// <summary>
        /// Gibt die Höhe der rechteckigen Hex-Karte (Anzahl Felder).
        /// </summary>
        /// <value>
        /// The height.
        /// </value>
        public int Height { get { return _map.GetLength(0);} }

        /// <summary>
        /// Gibt die Breite der rechteckigen Hex-Karte (Anzahl Felder in einer 'Reihe' / Anzahl Spalten).
        /// </summary>
        /// <value>
        /// The width.
        /// </value>
        public int Width { get { return _map.GetLength(1); } }

        #endregion


        #region Konstruktoren

        /// <summary>
        /// Initialisiert eine neue rechteckige Karte von "flachen" Hexagonen
        /// entsprechend der angegebenen Größe. Alle Hexagone sind mit dem
        /// Standard Konstruktor initialisert.
        /// </summary>
        /// <param name="rows">Anzahl Zeilen der Karte</param>
        /// <param name="cols">Anzahl Spalten der Karte</param>
        public HexMapRectFlat(int rows, int cols) {
            _map = new T[rows, cols];
            for (int r = 0; r < rows; r++) {
                for (int c = 0; c < cols; c++) {
                    _map[r, c] = new T { Koordinate = new HexKoordinate(r - c/2, c) };
                }
            }
            //_map.Initialize();
        }

        /// <summary>
        /// Initialisiert eine neue rechteckige Karte von "flachen" Hexagonen mit den
        /// angegebenen Elementen. Die Anzahl der Elemente sollte ganz durch <c>rows</c>
        /// teilbar sein.
        /// </summary>
        /// <param name="fields">The fields.</param>
        /// <param name="rows">The row count.</param>
        public HexMapRectFlat(ICollection<T> fields, int rows) {
            _map = new T[rows, fields.Count / rows];
            int i = 0;
            foreach (var hex in fields) {
                _map[(i / rows), (i % rows)] = hex;
                i++;
            }
        }

        #endregion


        #region Methoden

        /// <summary>
        /// Gibt an, ob die spezifizierte Koordinate in der Karte liegt.
        /// </summary>
        /// <param name="row">The row.</param>
        /// <param name="col">The col.</param>
        /// <returns>true, wenn Koordinate ok; false, wenn außerhalb der Karte</returns>
        /// <remarks>Aubfrage verhindert IndexOutOfRangeException beim Indexer.</remarks>
        public bool Contains(int row, int col) {
            int r = row + (col / 2);
            if (r < 0 || r >= this._map.GetLength(0))
                return false;
            if (col < 0 || col >= _map.GetLength(1))
                return false;
            return true;
        }

        /// <summary>
        /// Gibt an, ob die spezifizierte Koordinate in der Karte liegt.
        /// </summary>
        /// <param name="coord">The coordinate.</param>
        /// <returns>true, wenn Koordinate ok; false, wenn außerhalb der Karte</returns>
        /// <remarks>Aubfrage verhindert IndexOutOfRangeException beim Indexer.</remarks>
        public bool Contains(HexKoordinate coord) {
            return Contains(coord.Row, coord.Col);
        }
                
        #endregion

        
        #region IEnumerable<T>

        /// <summary>
        /// Returns an enumerator that iterates through the collection.<para>
        /// Durchläuft das Kartenrechteck zeilenweise: mit (0,0) beginnend SO, NO, SO, NO, unsw.,
        /// dann weiter mit (1,0) SO, NO, usw.
        /// </para>
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator() {
            for (int r = 0; r < _map.GetLength(0); r++) {
                for (int c = 0; c < _map.GetLength(1); c++) {
                    yield return _map[r, c];
                }
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }

        /// <summary>
        /// Gibt den Standardnumerator der eine Kartenzeile im Zickzack durchläuft.
        /// </summary>
        /// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.</returns>
        /// <seealso cref="T:Bettzueche.Dampfross.HexMapRectFlat`1#GetEnumerator"/>
        public IEnumerator<T> GetEnumeratorZickZackZeile() {
            return this.GetEnumerator();
        }

        /// <summary>
        /// Gibt einen Enumerator der die Karte Spaltenweise durchläuft.
        /// </summary>
        /// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.</returns>
        public IEnumerator<T> GetEnumeratorSpaltenweise() {
            for (int c = 0; c < _map.GetLength(1); c++) {
                for (int r = 0; r < _map.GetLength(0); r++) {
                    yield return _map[r, c];
                }
            }
        }

        #endregion

    }

    /// <summary>
    /// Sammlung statischer Methoden für eine rechteckigen <see cref="HexMapRectFlat{T}">Hexagon-Karte</see>.
    /// </summary>
    [Serializable]
    public class HexMapRectFlat
    {
        protected static readonly Dictionary<HexKoordinate, Richtung> DirMap; // Abbildung Move-Koords zu Richtung

        /// <summary>Sortiert von NO gegen Uzgs. nach SO.</summary>
        protected static readonly HexKoordinate[] MovesArr;

        static HexMapRectFlat() {
            MovesArr =  new HexKoordinate[] { 
                new HexKoordinate(-1, +1), new HexKoordinate(-1, 0), new HexKoordinate(0, -1),
                new HexKoordinate(+1, -1), new HexKoordinate(+1, 0), new HexKoordinate(0, +1),
            };
            DirMap = new Dictionary<HexKoordinate, Richtung>(MovesArr.Length + 2);
            DirMap.Add(MovesArr[0], Richtung.NO);
            DirMap.Add(MovesArr[1], Richtung.N);
            DirMap.Add(MovesArr[2], Richtung.NW);
            DirMap.Add(MovesArr[3], Richtung.SW);
            DirMap.Add(MovesArr[4], Richtung.S);
            DirMap.Add(MovesArr[5], Richtung.SO);
        }

        public static int HexDistance(int c1, int r1, int c2, int r2) {
            int dist = Math.Abs(c1-c2) + Math.Abs(c2 - c1 + r2 - r1) + Math.Abs(r1-r2);
            return dist >> 1; // durch 2
        }
        public static int HexDistance(HexKoordinate a, HexKoordinate b) {
            return HexDistance(a.Col, a.Row, b.Col, b.Row);
        }


        /// <summary>
        /// Gibt die einzuschlagende <c>Richtung</c> von der angegebenen Koordinate zum spezifizierten Ziel.
        /// </summary>
        /// <param name="von">Von-Koordinate</param>
        /// <param name="nach">Nach-Koordinate</param>
        /// <returns>Die einzuschlagende <see cref="Bettzueche.Dampfross.Richtung">Richtung</see>.
        /// null, wenn <c>von = nach</c>.</returns>
        public static Richtung GibRichtung(HexKoordinate von, HexKoordinate nach) {
            var to = nach - von;
            if (to.Row == 0 && to.Col == 0) {
                // von = nach -> auf Feld stehenbleiben
                return null;
            }
            var dirCoord = NextHexToTarget(0, 0, to.Col, to.Row);
            return DirMap[dirCoord];
        }
        /// <summary>
        /// Gibt die einzuschlagende <c>HexRichtung</c> von der angegebenen Koordinate zum spezifizierten Ziel.
        /// </summary>
        /// <param name="von">The von.</param>
        /// <param name="nach">The nach.</param>
        /// <returns>Die einzuschlagende <c>HexRichtung</c> von der angegebenen Koordinate zum spezifizierten Ziel.
        /// Falls von = nach, dann wird standardmäßig die erste Richtung (NO) zurückgeliefert.</returns>
        public static HexRichtungen GibHexRichtung(HexKoordinate von, HexKoordinate nach) {
            if (von == nach) {
                return HexRichtungen.NO;
            }
            return GibRichtung(von, nach).Dir; // Methode gäbe 'null' für 'von==nach'
        }

        /// <summary>
        /// Gibt die Folgekoordinate, wenn man vom angegebenen Feld um <c>steps</c> Schritte
        /// in die spezifizierte Richtung geht.
        /// </summary>
        /// <param name="from">Koordinate des Ausgangsfelds</param>
        /// <param name="dir">Die Richtung (not null).</param>
        /// <param name="steps">Anzahl der Schritte</param>
        /// <returns>Folgekoordinate</returns>
        public static HexKoordinate Move(HexKoordinate from, Richtung dir, int steps) {
            return from + (MovesArr[dir] * steps);
        }
        /// <summary>
        /// Gibt die Folgekoordinate, wenn man vom angegebenen Feld in die spezifizierte Richtung geht.
        /// </summary>
        /// <param name="from">Koordinate des Ausgangsfelds</param>
        /// <param name="dir">Die Richtung (not null).</param>
        /// <returns>Folgekoordinate</returns>
        public static HexKoordinate Move(HexKoordinate from, Richtung dir) {
            return from + MovesArr[dir];
        }
        /// <summary>
        /// Gibt die Folgekoordinate, wenn man vom angegebenen Feld in die spezifizierte Richtung geht.
        /// </summary>
        /// <param name="from">Koordinate des Ausgangsfelds</param>
        /// <param name="dir">Die Richtung.</param>
        /// <returns>Folgekoordinate</returns>
        public static HexKoordinate Move(HexKoordinate from, HexRichtungen dir) {
            return from + MovesArr[(int)dir];
        }

        #region Hilfsmethoden

        static HexKoordinate NextHexToTarget(int cA, int rA, int cB, int rB) {
            int dist = HexDistance(cA, rA, cB, rB);
            if (dist == 0) {
                return new HexKoordinate(rA, cA);
            }
            double bruch = 1.0 / dist;
            double realC = cA * (1.0 - bruch) + cB * bruch;
            double realR = rA * (1.0 - bruch) + rB * bruch;
            return HexRound(realC, realR);
        }

        static HexKoordinate HexRound(double rc, double rr) {
            CubeKoordinate cube = HexRound(rc, -(rc + rr), rr);
            var axHex = new HexKoordinate(cube.Z, cube.X);
            return axHex;
        }

        static CubeKoordinate HexRound(double rx, double ry, double rz) {
            CubeKoordinate cube = new CubeKoordinate();
            // Note: Math.Round, damit negative Zahlen korrekt gerundet werden.
            cube.X = (int)Math.Round(rx);
            cube.Y = (int)Math.Round(ry);
            cube.Z = (int)Math.Round(rz); // (int)(-1 + 0.5) = (int)-0.5 -> 0 !!! 
            if (cube.Sum() != 0) {
                if (cube.Y == cube.Z) {
                    cube.Z = -(cube.X + cube.Y);
                }
                else if (cube.X == cube.Y) {
                    cube.Y = -(cube.X + cube.Z);
                }
                else { // if cube[0] == cube[2]
                    cube.X = -(cube.Y + cube.Z);
                }
            }
            return cube;
        }

        #endregion

    }
}
